package com.xh3140.xwidget.button;

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.InsetDrawable;
import android.graphics.drawable.RippleDrawable;
import android.graphics.drawable.StateListDrawable;
import android.util.AttributeSet;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.annotation.Size;
import androidx.appcompat.widget.AppCompatTextView;

import java.util.Arrays;

/**
 * A built-in color style, easy to configure the style button widget.
 */
@SuppressWarnings("unused")
public class Button extends AppCompatTextView {
  private final static int[] states_enabled = {android.R.attr.state_enabled};
  private final static int[] states_pressed = {android.R.attr.state_pressed};
  private final static int[] states_disabled = {-android.R.attr.state_enabled};
  private final static int[][] states_foreground = {states_enabled, states_pressed, states_disabled};

  private ColorStyle mColorStyle;
  private boolean mIsRippleDrawable = true;
  private boolean mIsInsetDrawable = true;
  private int[] mRadius = {0, 0, 0, 0};
  private int[] mInsets = {0, 0, 0, 0};

  /**
   * Button color style interface.
   * The color used to configure the button,
   * which can be configured by the user.
   */
  public interface ColorStyle {
    /**
     * Returns the foreground colors(argb) of the button.
     * For button, this refers to the color of the text.
     * Color at 0: Refers to the foreground color in the {@code android.R.attr.state_enabled}.
     * Color at 1: Refers to the foreground color in the {@code android.R.attr.state_pressed}.
     * Color at 2: Refers to the foreground color in the {@code -android.R.attr.state_enabled}.
     *
     * @return the foreground colors of the button.
     */
    @NonNull
    @Size(3)
    int[] foreground();

    /**
     * Returns the background colors(argb) of the button.
     * Color at 0: Refers to the background color in the {@code android.R.attr.state_enabled}.
     * Color at 1: Refers to the background color in the {@code android.R.attr.state_pressed}.
     * Color at 2: Refers to the background color in the {@code -android.R.attr.state_enabled}.
     *
     * @return the background colors of the button.
     */
    @NonNull
    @Size(3)
    int[] background();
  }

  /**
   * 颜色样式枚举类
   * 可以添加更多好看的主题
   */
  public enum BuiltInColorStyle implements ColorStyle {
    LIGHT(new int[]{0xFF212529, 0xFF212529, 0xFF212529}, new int[]{0xFFF8F9FA, 0xFFE2E6EA, 0xFFFBFBFC}),
    DARK(new int[]{0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}, new int[]{0xFF343A40, 0xFF23272B, 0xFF999C9F}),
    GRAY(new int[]{0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}, new int[]{0xFF6C757D, 0xFF5A6268, 0xFFB5B9BD}),
    RED(new int[]{0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}, new int[]{0xFFDC3545, 0xFFC82333, 0xFFED99A1}),
    GREEN(new int[]{0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}, new int[]{0xFF28A745, 0xFF218838, 0xFF93D2A1}),
    BLUE(new int[]{0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}, new int[]{0xFF007BFF, 0xFF0069A9, 0xFF7FBCFF}),
    CYAN(new int[]{0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}, new int[]{0xFF17A2B8, 0xFF138496, 0xFF8AD0DB}),
    YELLOW(new int[]{0xFF212529, 0xFF212529, 0xFF212529}, new int[]{0xFFFFC107, 0xFFE0A800, 0xFFFFDF82}),
    PURPLE(new int[]{0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}, new int[]{0xFF6200EE, 0xFF3700B3, 0xFFB07FF6}),
    PINK(new int[]{0xFF800000, 0xFF800000, 0xFF800000}, new int[]{0xFFFFB6C1, 0xFFFFC0CB, 0xFFFFDADF}),
    GOLD(new int[]{0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}, new int[]{0xFFFFD700, 0xFFDAA520, 0xFFFFEA7F}),
    ORANGE(new int[]{0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}, new int[]{0xFFFFA500, 0xFFFF8C00, 0xFFFFD17F}),
    OLIVE(new int[]{0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}, new int[]{0xFF808000, 0xFF556B2F, 0xFFBFBF7F}),
    BROWN(new int[]{0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}, new int[]{0xFFA52A2A, 0xFF800000, 0xFFD19494});

    private final int[] mForeground;
    private final int[] mBackground;

    BuiltInColorStyle(int[] foreground, int[] background) {
      mForeground = foreground;
      mBackground = background;
    }

    @NonNull
    @Size(3)
    @Override
    public int[] foreground() {
      return mForeground;
    }

    @NonNull
    @Size(3)
    @Override
    public int[] background() {
      return mBackground;
    }
  }

  public Button(@NonNull Context context) {
    this(context, null);
  }

  public Button(@NonNull Context context, @Nullable AttributeSet attrs) {
    this(context, attrs, R.attr.buttonStyle);
  }

  public Button(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Button, 0, R.style.Button);
    try {
      if (a.hasValue(R.styleable.Button_builtInColorStyle)) {
        final int index = a.getInt(R.styleable.Button_builtInColorStyle, -1);
        mColorStyle = BuiltInColorStyle.values()[index];
      }
      mIsRippleDrawable = a.getBoolean(R.styleable.Button_isRippleDrawable, mIsRippleDrawable);
      mIsInsetDrawable = a.getBoolean(R.styleable.Button_isInsetDrawable, mIsInsetDrawable);
      if (a.hasValue(R.styleable.Button_radius)) {
        final int radius = a.getDimensionPixelSize(R.styleable.Button_radius, -1);
        mRadius = new int[]{radius, radius, radius, radius};
      } else {
        mRadius[0] = a.getDimensionPixelSize(R.styleable.Button_radius_leftTop, mRadius[0]);
        mRadius[1] = a.getDimensionPixelSize(R.styleable.Button_radius_rightTop, mRadius[1]);
        mRadius[2] = a.getDimensionPixelSize(R.styleable.Button_radius_rightBottom, mRadius[2]);
        mRadius[3] = a.getDimensionPixelSize(R.styleable.Button_radius_leftBottom, mRadius[3]);
      }
      if (a.hasValue(R.styleable.Button_inset)) {
        final int inset = a.getDimensionPixelSize(R.styleable.Button_inset, -1);
        mInsets = new int[]{inset, inset, inset, inset};
      } else {
        mInsets[0] = a.getDimensionPixelSize(R.styleable.Button_inset_left, mInsets[0]);
        mInsets[1] = a.getDimensionPixelSize(R.styleable.Button_inset_top, mInsets[1]);
        mInsets[2] = a.getDimensionPixelSize(R.styleable.Button_inset_right, mInsets[2]);
        mInsets[3] = a.getDimensionPixelSize(R.styleable.Button_inset_bottom, mInsets[3]);
      }
    } finally {
      a.recycle();
      updateForegroundInner();
      updateBackgroundInner();
    }
  }

  /**
   * Create a {@link GradientDrawable} instance.
   *
   * @param backgroundColor background color, argb.
   * @return a new {@link GradientDrawable} instance.
   */
  private Drawable newContentDrawable(int backgroundColor) {
    final GradientDrawable drawable = new GradientDrawable();
    drawable.setColor(backgroundColor);
    drawable.setCornerRadii(new float[]{mRadius[0], mRadius[0], mRadius[1], mRadius[1], mRadius[2], mRadius[2], mRadius[3], mRadius[3]});
    return mIsInsetDrawable ? new InsetDrawable(drawable, mInsets[0], mInsets[1], mInsets[2], mInsets[3]) : drawable;
  }

  /**
   * update the button foreground color.
   * By setting the button text color.
   *
   * @see android.widget.TextView#setTextColor(int)
   */
  private void updateForegroundInner() {
    setTextColor(new ColorStateList(states_foreground, mColorStyle.foreground()));
  }

  /**
   * update the button background color.
   * By setting the button background drawable.
   *
   * @see android.view.View#setBackground(Drawable)
   */
  private void updateBackgroundInner() {
    final StateListDrawable drawable = new StateListDrawable();
    final int[] background = mColorStyle.background();
    if (mIsRippleDrawable) {
      drawable.addState(states_enabled, new RippleDrawable(ColorStateList.valueOf(background[1]),
              newContentDrawable(background[0]), null));
    } else {
      drawable.addState(states_enabled, newContentDrawable(background[0]));
      drawable.addState(states_pressed, newContentDrawable(background[1]));
    }
    drawable.addState(states_disabled, newContentDrawable(background[2]));
    setBackground(drawable);
  }

  /**
   * Returns the button color style.
   *
   * @return the button color style.
   */
  @NonNull
  public ColorStyle getColorStyle() {
    return mColorStyle;
  }

  /**
   * Sets the button color style.
   *
   * @param colorStyle the button color style.
   */
  public void setColorStyle(@NonNull ColorStyle colorStyle) {
    if (mColorStyle != colorStyle) {
      mColorStyle = colorStyle;
      updateForegroundInner();
      updateBackgroundInner();
    }
  }

  /**
   * Returns {@code true} if the button background is a ripple drawable, {@code false} otherwise.
   * If true, corresponding to {@link RippleDrawable}.
   * If false, corresponding to {@link StateListDrawable}.
   *
   * @return {@code true} if the button background is a ripple drawable, {@code false} otherwise.
   */
  public boolean isRippleDrawable() {
    return mIsRippleDrawable;
  }

  /**
   * Sets whether the button background is ripple drawable.
   * If true, corresponding to {@link RippleDrawable}.
   * If false, corresponding to {@link StateListDrawable}.
   *
   * @param isRippleDrawable whether the button background is ripple drawable.
   */
  public void setIsRippleDrawable(boolean isRippleDrawable) {
    if (mIsRippleDrawable != isRippleDrawable) {
      mIsRippleDrawable = isRippleDrawable;
      updateBackgroundInner();
    }
  }

  /**
   * Returns {@code true} if the button background is a inset drawable, {@code false} otherwise.
   * If true, corresponding to {@link InsetDrawable}.
   * If false, corresponding to {@link GradientDrawable}.
   *
   * @return {@code true} if the button background is a inset drawable, {@code false} otherwise.
   */
  public boolean isInsetDrawable() {
    return mIsInsetDrawable;
  }

  /**
   * Sets whether the button background is inset drawable.
   * If true, corresponding to {@link InsetDrawable}.
   * If false, corresponding to {@link GradientDrawable}.
   *
   * @param isInsetDrawable whether the button background is inset drawable.
   */
  public void setIsInsetDrawable(boolean isInsetDrawable) {
    if (mIsInsetDrawable != isInsetDrawable) {
      mIsInsetDrawable = isInsetDrawable;
      updateBackgroundInner();
    }
  }

  /**
   * Returns the radius of the four corners of the button.
   * Format: [leftTop, rightTop, rightBottom, leftBottom] px.
   *
   * @return the radius of the four corners of the button.
   */
  @NonNull
  public int[] getRadius() {
    return Arrays.copyOf(mRadius, 4);
  }

  /**
   * Sets the radius of the four corners of the button.
   * Format: [leftTop, rightTop, rightBottom, leftBottom] px.
   *
   * @param radius the radius of the four corners of the button.
   */
  public void setRadius(@NonNull @Size(4) int[] radius) {
    if (mRadius[0] != radius[0] || mRadius[1] != radius[1] || mRadius[2] != radius[2] || mRadius[3] != radius[3]) {
      mRadius = radius;
      updateBackgroundInner();
    }
  }

  /**
   * Sets the radius of the four corners of the button.
   *
   * @param radius the radius of the four corners of the button.
   */
  public void setRadius(@Px int radius) {
    setRadius(new int[]{radius, radius, radius, radius});
  }

  /**
   * Sets the radius of the four corners of the button.
   *
   * @param leftTop     radius of left top, px.
   * @param rightTop    radius of right top, px.
   * @param rightBottom radius of right bottom, px.
   * @param leftBottom  radius of left bottom, px.
   */
  public void setRadius(@Px int leftTop, @Px int rightTop, @Px int rightBottom, @Px int leftBottom) {
    setRadius(new int[]{leftTop, rightTop, rightBottom, leftBottom});
  }

  /**
   * Returns the inset distance of the four edges of the button.
   * Format: [left, top, right, bottom] px.
   *
   * @return the inset distance of the four edges of the button.
   */
  @NonNull
  public int[] getInsets() {
    return Arrays.copyOf(mInsets, 4);
  }

  /**
   * Sets the inset distance of the four edges of the button.
   * Format: [left, top, right, bottom] px.
   *
   * @param insets the inset distance of the four edges of the button.
   */
  public void setInsets(@NonNull @Size(4) int[] insets) {
    if (mInsets[0] != insets[0] || mInsets[1] != insets[1] || mInsets[2] != insets[2] || mInsets[3] != insets[3]) {
      mInsets = insets;
      updateBackgroundInner();
    }
  }

  /**
   * Sets the inset distance of the four edges of the button.
   *
   * @param insets the inset distance of the four edges of the button.
   */
  public void setInsets(@Px int insets) {
    setInsets(new int[]{insets, insets, insets, insets});
  }

  /**
   * Sets the inset distance of the four edges of the button.
   *
   * @param left   inset distance of left, px.
   * @param top    inset distance of top, px.
   * @param right  inset distance of right, px.
   * @param bottom inset distance of bottom, px.
   */
  public void setInsets(@Px int left, @Px int top, @Px int right, @Px int bottom) {
    setInsets(new int[]{left, top, right, bottom});
  }
}




